<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SVG Topology Editor</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      margin: 0;
    }
    svg {
      border: 1px solid #ccc;
    }
    .connector {
      fill: red;
      cursor: pointer;
    }
    .selected {
      stroke: blue;
    }
    .link {
      stroke: black;
      stroke-width: 2;
      fill: none;
    }
    .grid {
      stroke: lightgray;
      stroke-width: 0.5;
    }
  </style>
</head>
<body>
  <svg id="canvas" width="800" height="600"></svg>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const svg = document.getElementById('canvas');
      const gridSize = 20;

      drawGrid(svg, gridSize);

      let nodes = [];
      let links = [];
      let selectedLink = null;
      let selectedNode = null;
      let isDragging = false;
      let dragOffset = { x: 0, y: 0 };
      let currentLine = null;

      svg.addEventListener('mousedown', onMouseDown);
      svg.addEventListener('mousemove', onMouseMove);
      svg.addEventListener('mouseup', onMouseUp);

      createNode(100, 100);
      createNode(300, 200);

      function drawGrid(svg, size) {
        for (let x = 0; x < svg.clientWidth; x += size) {
          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
          line.setAttribute('x1', x);
          line.setAttribute('y1', 0);
          line.setAttribute('x2', x);
          line.setAttribute('y2', svg.clientHeight);
          line.classList.add('grid');
          svg.appendChild(line);
        }

        for (let y = 0; y < svg.clientHeight; y += size) {
          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
          line.setAttribute('x1', 0);
          line.setAttribute('y1', y);
          line.setAttribute('x2', svg.clientWidth);
          line.setAttribute('y2', y);
          line.classList.add('grid');
          svg.appendChild(line);
        }
      }

      function createNode(x, y) {
        const node = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        node.setAttribute('transform', `translate(${x}, ${y})`);
        node.classList.add('node');

        const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rect.setAttribute('width', 100);
        rect.setAttribute('height', 50);
        rect.setAttribute('fill', 'lightblue');

        const connectorTop = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        connectorTop.classList.add('connector');
        connectorTop.setAttribute('cx', 50);
        connectorTop.setAttribute('cy', 0);
        connectorTop.setAttribute('r', 5);

        const connectorBottom = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        connectorBottom.classList.add('connector');
        connectorBottom.setAttribute('cx', 50);
        connectorBottom.setAttribute('cy', 50);
        connectorBottom.setAttribute('r', 5);

        node.appendChild(rect);
        node.appendChild(connectorTop);
        node.appendChild(connectorBottom);

        svg.appendChild(node);
        nodes.push(node);

        node.addEventListener('mousedown', onNodeMouseDown);

        function onNodeMouseDown(event) {
          selectedNode = node;
          const transform = node.getAttribute('transform');
          const translate = transform.match(/translate\(([^,]+),\s*([^\)]+)\)/);
          const startX = parseFloat(translate[1]);
          const startY = parseFloat(translate[2]);
          dragOffset.x = event.clientX - startX;
          dragOffset.y = event.clientY - startY;
          isDragging = true;
        }
      }

      function onMouseDown(event) {
        if (event.target.classList.contains('connector')) {
          const startNode = event.target.closest('.node');
          const startConnector = event.target;
          currentLine = createLink(startNode, startConnector, event);
          svg.appendChild(currentLine);
        }
      }

      function onMouseMove(event) {
        if (isDragging && selectedNode) {
          const newX = event.clientX - dragOffset.x;
          const newY = event.clientY - dragOffset.y;
          selectedNode.setAttribute('transform', `translate(${newX}, ${newY})`);
          updateLinks(selectedNode);
        } else if (currentLine) {
          updateLinkPath(currentLine, currentLine.startNode, currentLine.startConnector, { getBoundingClientRect: () => ({ left: event.clientX, top: event.clientY, width: 0, height: 0 }) }, { getBoundingClientRect: () => ({ left: event.clientX, top: event.clientY, width: 0, height: 0 }) });
        }
      }

      function onMouseUp(event) {
        if (currentLine) {
          const endConnector = event.target;
          if (endConnector.classList.contains('connector')) {
            const endNode = endConnector.closest('.node');
            currentLine.endNode = endNode;
            currentLine.endConnector = endConnector;
            updateLinkPath(currentLine, currentLine.startNode, currentLine.startConnector, currentLine.endNode, currentLine.endConnector);
            links.push(currentLine);
          } else {
            svg.removeChild(currentLine);
          }
          currentLine = null;
        }
        isDragging = false;
        selectedNode = null;
      }

      function createLink(startNode, startConnector, event) {
        const link = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        link.classList.add('link');
        link.startNode = startNode;
        link.startConnector = startConnector;
        link.endNode = null;
        link.endConnector = null;

        const tempEnd = { getBoundingClientRect: () => ({ left: event.clientX, top: event.clientY, width: 0, height: 0 }) };
        updateLinkPath(link, startNode, startConnector, tempEnd, tempEnd);

        link.addEventListener('click', onLinkClick);
        link.addEventListener('contextmenu', onLinkRightClick);

        return link;
      }

      function updateLinks(node) {
        links.forEach(link => {
          if (link.startNode === node || link.endNode === node) {
            updateLinkPath(link, link.startNode, link.startConnector, link.endNode, link.endConnector);
          }
        });
      }

      function onLinkClick(event) {
        if (selectedLink) {
          selectedLink.classList.remove('selected');
        }
        selectedLink = event.target;
        selectedLink.classList.add('selected');
      }

      function onLinkRightClick(event) {
        event.preventDefault();
        if (selectedLink) {
          svg.removeChild(selectedLink);
          links = links.filter(link => link !== selectedLink);
          selectedLink = null;
        }
      }
    });
  </script>
</body>
</html>
